home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2007 December / PCWKCD1207B.iso / Blogowanie poza sfera / Flock 1.0 beta / flock-1.0RC3.en-US.win32.exe / flock / modules / deliciousApi.jsm next >
Text File  |  2007-10-18  |  13KB  |  386 lines

  1. // BEGIN FLOCK GPL
  2. // 
  3. // Copyright Flock Inc. 2005-2007
  4. // http://flock.com
  5. // 
  6. // This file may be used under the terms of of the
  7. // GNU General Public License Version 2 or later (the "GPL"),
  8. // http://www.gnu.org/licenses/gpl.html
  9. // 
  10. // Software distributed under the License is distributed on an "AS IS" basis,
  11. // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12. // for the specific language governing rights and limitations under the
  13. // License.
  14. // 
  15. // END FLOCK GPL
  16.  
  17. // This is a library to access any bookmark service through the Delicious API.
  18. // Many services are providing a compatibility layer with Delicious,
  19. // so this library is used also for Magnolia and Shadows.
  20.  
  21. var EXPORTED_SYMBOLS = ["DeliciousAPI"];
  22.  
  23. const CC = Components.classes;
  24. const CI = Components.interfaces;
  25. const CR = Components.results;
  26. const CU = Components.utils;
  27.  
  28. CU.import("resource:///modules/ISO8601DateUtils.jsm");
  29.  
  30. /**************************************************************************
  31.  * Module: Delicious API
  32.  **************************************************************************/
  33.  
  34. // Constructor.
  35. function DeliciousAPI(aUrl, aLogger) {
  36.   this._url = aUrl;
  37.   this._logger = aLogger;
  38. }
  39.  
  40. // Private data.
  41. DeliciousAPI.prototype._url = "";
  42. DeliciousAPI.prototype._logger = null;
  43.  
  44. /**************************************************************************
  45.  * Delicious API Public Interface
  46.  **************************************************************************/
  47.  
  48. /**
  49.  * Call the specified del.ico.us API method.
  50.  * @param AString aAPIMethod (in)
  51.  *    One of the API methods; "posts/add", etc.
  52.  * @param object aArgs (in)
  53.  *    As documented at http://del.icio.us/help/api/
  54.  * @param object aAPIListener (in)
  55.  *    An object (deliciousAPIListener) with the following two methods:
  56.  *      void function onSuccess(in nsIDOMDocument aXML)
  57.  *      void function onError(in flockIError aError)
  58.  * @param object aAuth (in)
  59.  *    An object with the following two properties:
  60.  *      AString user
  61.  *      AString password
  62.  * @return nothing
  63.  */
  64. DeliciousAPI.prototype.call =
  65. function deliciousAPI_call(aAPIMethod, aArgs, aAPIListener, aAuth) {
  66.   this._logger.debug("deliciousAPI_call('" + aAPIMethod + "', aArgs,"
  67.                      + " aAPIListener, aAuth)");
  68.  
  69.   // Convert args from object notation to an array of "key=value" pairs,
  70.   // performing any escaping necessary for use in URL.
  71.   var argsArray = [];
  72.   for (var key in aArgs) {
  73.     argsArray.push(encodeURIComponent(key) + "="
  74.                    + encodeURIComponent(aArgs[key]));
  75.   }
  76.  
  77.   // Build the complete API command: host, API method, and args.
  78.   var url = this._url + aAPIMethod;
  79.   if (argsArray.length) {
  80.     url = url + "?" +argsArray.join("&");
  81.   }
  82.  
  83.   // Build an XMLHTTPRequest for sending the API call.
  84.   var request = CC["@mozilla.org/xmlextras/xmlhttprequest;1"]
  85.                 .createInstance(CI.nsIXMLHttpRequest);
  86.  
  87.   var onReadyStateFunc = function deliciousAPI_call_onReadyStateFunc(eEvt) {
  88.     if (request.readyState == 4) {
  89.       if (request.status >= 200 && request.status < 300) {
  90.         // Scrub input and create an nsIDOMDocument.
  91.         var text = request.responseText.replace(/[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F]/g, "");
  92.         if (!text) {
  93.           text = "<?xml version='1.0' standalone='yes'?>";
  94.         }
  95.         var xml = CC["@mozilla.org/xmlextras/domparser;1"]
  96.                   .createInstance(CI.nsIDOMParser)
  97.                   .parseFromString(text, "text/xml");
  98.         aAPIListener.onSuccess(xml);
  99.       } else {
  100.         var error = CC["@flock.com/error;1"].createInstance(CI.flockIError);
  101.         error.serviceErrorCode = request.status;
  102.         error.serviceErrorString = request.responseText;
  103.         switch (request.status) {
  104.           case 401:
  105.             // Bad login/password
  106.             error.errorCode = CI.flockIError.FAVS_INVALID_AUTH;
  107.             break;
  108.           case 503:
  109.             // Service unavailable
  110.             error.errorCode = CI.flockIError.FAVS_UNAVAILABLE;
  111.             break;
  112.           default:
  113.             // Unknown error code
  114.             error.errorCode = CI.flockIError.FAVS_UNKNOWN_ERROR;
  115.             break;
  116.         }
  117.         aAPIListener.onError(error);
  118.       }
  119.     }
  120.   };
  121.  
  122.   request.onreadystatechange = onReadyStateFunc;
  123.   request.backgroundRequest = true;
  124.   request.overrideMimeType("text/txt");
  125.   if (aAuth) {
  126.     request.open("GET", url, true, aAuth.user, aAuth.password);
  127.   } else {
  128.     request.open("GET", url, true);
  129.   }
  130.   this._logger.debug("deliciousAPI_call request URL: '" + url + "'");
  131.   request.send(null);
  132. };
  133.  
  134. /**
  135.  * Call the del.ico.us "posts/all" API method.
  136.  * @param flockIListener aFlockListener (in)
  137.  * @param aAuth (in)
  138.  *    An object with the following two properties:
  139.  *      AString user
  140.  *      AString password
  141.  * @param AString aSeparator (in)
  142.  *    The delimiter to expect in the returned list of tags.
  143.  * @return nothing
  144.  *
  145.  * Except when calling for the first time, "posts/update" should be called
  146.  * before calling this, so that only new posts are returned.
  147.  *
  148.  * @see flockIListener
  149.  */
  150. DeliciousAPI.prototype.postsAll =
  151. function deliciousAPI_postsAll(aFlockListener, aAuth, aSeparator, aUpdateTime) 
  152. {
  153.  
  154.   var tagSep = " ";
  155.   if (aSeparator) {
  156.     tagSep = aSeparator;
  157.   }
  158.  
  159.   var api = this;
  160.   var deliciousAPIListener = {
  161.     onError: function deliciousAPI_postsAll_onError(aError) {
  162.       if (aFlockListener) {
  163.         aFlockListener.onError(null, "error", aError);
  164.       }
  165.     },
  166.     onSuccess: function deliciousAPI_postsAll_onSuccess(aXML) {
  167.       // Validate the nsIDOMDocument response.
  168.       if (!api.isExpectedResponse(aXML, "posts")) {
  169.         api._logger.error("posts/all succeeded, bad xml response");
  170.  
  171.         var error = CC["@flock.com/error;1"].createInstance(CI.flockIError);
  172.  
  173.         // FIXME: FAVS_, not OPML_. Clean up flockIError.idl.
  174.         error.errorCode = CI.flockIError.OPML_INVALID_XML;
  175.         if (aXML && aXML.documentElement) {
  176.           api._logger.debug("tagName is: " + aXML.documentElement.tagName);
  177.           error.serviceErrorString = aXML.documentElement.tagName;
  178.         } else {
  179.           api._logger.debug("aXML.documentElement is not defined");
  180.           error.serviceErrorString = "aXML.documentElement is not defined";
  181.         }
  182.         aFlockListener.onError(null, "error", error);
  183.  
  184.       } else {
  185.  
  186.         var result = [];
  187.         var children = aXML.documentElement.childNodes;
  188.         for (var i=0; i<children.length; i++) {
  189.           var child = children.item(i);
  190.           if (child.nodeType != 1) {
  191.             continue;
  192.           }
  193.           child.QueryInterface(CI.nsIDOMElement);
  194.           if (child.tagName != "post") {
  195.             continue;
  196.           }
  197.           if ((child.getAttribute("href") == "about:blank") ||
  198.               (child.getAttribute("href") == ""))
  199.           {
  200.             continue;
  201.           }
  202.           result.push({
  203.             URL: child.getAttribute("href"),
  204.             name: child.getAttribute("description"),
  205.             description: child.getAttribute("extended"),
  206.             tags: child.getAttribute("tag"),
  207.             tag: child.getAttribute("tag").split(tagSep),
  208.             shared: child.getAttribute("shared"),
  209.             datevalue: ISO8601DateUtils.parse(child.getAttribute("time")),
  210.             hash: child.getAttribute("hash")
  211.           });
  212.         }
  213.         aFlockListener.onSuccess(result, aUpdateTime);
  214.       }
  215.     }
  216.   };
  217.  
  218.   this.call("posts/all", {}, deliciousAPIListener, aAuth);
  219. };
  220.  
  221. /**
  222.  * Call the del.ico.us "posts/update" API method.
  223.  * @param flockIListener aFlockListener (in)
  224.  * @param object aAuth (in)
  225.  *    An object with the following two properties:
  226.  *      AString user
  227.  *      AString password
  228.  * @param Date() aLastUpdate (in)
  229.  *    The last time we retrieved bookmarks from the server.
  230.  * @param AString aSeparator (in)
  231.  *    The delimiter to expect in the returned list of tags.
  232.  * @return nothing
  233.  *
  234.  * @see flockIListener
  235.  */
  236. DeliciousAPI.prototype.postsUpdate =
  237. function deliciousAPI_postsUpdate(aFlockListener, aAuth,
  238.                                   aLastUpdate, aSeparator) {
  239.   var tagSep = " ";
  240.   if (aSeparator) {
  241.     tagSep = aSeparator;
  242.   }
  243.  
  244.   var api = this;
  245.   var deliciousAPIListener = {
  246.     onError: function deliciousAPI_postsUpdate_onError(aError) {
  247.       if (aFlockListener) {
  248.         aFlockListener.onError(null, "error", aError);
  249.       }
  250.     },
  251.     onSuccess: function deliciousAPI_postsUpdate_onSuccess(aXML) {
  252.       // Validate the nsIDOMDocument response.
  253.       if (!api.isExpectedResponse(aXML, "update")) {
  254.         api._logger.error('posts/update succeeded, bad xml response');
  255.  
  256.         var error = CC["@flock.com/error;1"].createInstance(CI.flockIError);
  257.  
  258.         // FIXME: FAVS_, not OPML_. Clean up flockIError.idl.
  259.         error.errorCode = CI.flockIError.OPML_INVALID_XML;
  260.         if (aXML && aXML.documentElement) {
  261.           api._logger.debug("tagName is: " + aXML.documentElement.tagName);
  262.           error.serviceErrorString = aXML.documentElement.tagName;
  263.         } else {
  264.           api._logger.debug("aXML.documentElement is not defined");
  265.           error.serviceErrorString = "aXML.documentElement is not defined";
  266.         }
  267.         aFlockListener.onError(null, "error", error);
  268.  
  269.       } else {
  270.  
  271.         var lastUpdate = ISO8601DateUtils.parse(aXML.documentElement
  272.                                          .getAttribute("time"));
  273.         api._logger.debug("Last update on the server  : " + lastUpdate);
  274.         api._logger.debug("Last time updates retrieved: " + aLastUpdate);
  275.         if (lastUpdate.getTime() > aLastUpdate.getTime()) {
  276.           // There is new/updated stuff on the server
  277.           if (api.timer) {
  278.             api.timer.cancel();
  279.           } else {
  280.             api.timer = CC["@mozilla.org/timer;1"]
  281.                         .createInstance(CI.nsITimer);
  282.           }
  283.           // Init a timer to start in 1.3 seconds.
  284.           api.timer.initWithCallback({
  285.             notify: function deliciousAPI_postsUpdateTimer_notify(aTimer) {
  286.               api.postsAll(aFlockListener, aAuth, tagSep, lastUpdate);
  287.             }
  288.           }, 1300, CI.nsITimer.TYPE_ONE_SHOT);
  289.         } else {
  290.           // No refresh needed
  291.           if (aFlockListener) {
  292.             aFlockListener.onSuccess(null, "nonew");
  293.           }
  294.         }
  295.       }
  296.     }
  297.   };
  298.  
  299.   this.call("posts/update", {}, deliciousAPIListener, aAuth);
  300. };
  301.  
  302. /**
  303.  * Call the del.ico.us "tags/get" API method.
  304.  * @param flockIListener aFlockListener (in)
  305.  * @param object aAuth (in)
  306.  *    An object with the following two properties:
  307.  *      AString user
  308.  *      AString password
  309.  * @return nothing
  310.  *
  311.  * @see flockIListener
  312.  */
  313. DeliciousAPI.prototype.tagsGet =
  314. function deliciousAPI_tagsGet(aFlockListener, aAuth) {
  315.  
  316.   var api = this;
  317.   var deliciousAPIListener = {
  318.     onError: function deliciousAPI_tagsGet_onError(aError) {
  319.       if (aFlockListener) {
  320.         aFlockListener.onError(null, "error", aError);
  321.       }
  322.     },
  323.     onSuccess: function deliciousAPI_tagsGet_onSuccess(aXML) {
  324.       // Validate the nsIDOMDocument response.
  325.       if (!api.isExpectedResponse(aXML, "tags")) {
  326.         api._logger.error('tags/get succeeded, bad xml response');
  327.  
  328.         var error = CC["@flock.com/error;1"].createInstance(CI.flockIError);
  329.  
  330.         // FIXME: FAVS_, not OPML_. Clean up flockIError.idl.
  331.         error.errorCode = CI.flockIError.OPML_INVALID_XML;
  332.         if (aXML && aXML.documentElement) {
  333.           api._logger.debug("tagName is: " + aXML.documentElement.tagName);
  334.           error.serviceErrorString = aXML.documentElement.tagName;
  335.         } else {
  336.           api._logger.debug("aXML.documentElement is not defined");
  337.           error.serviceErrorString = "aXML.documentElement is not defined";
  338.         }
  339.         aFlockListener.onError(null, "error", error);
  340.  
  341.       } else {
  342.  
  343.         var result = [];
  344.         var children = aXML.documentElement.childNodes;
  345.         for (var i=0; i<children.length; i++) {
  346.           var child = children.item(i);
  347.           if (child.nodeType != 1) {
  348.             continue;
  349.           }
  350.           child.QueryInterface(CI.nsIDOMElement);
  351.           if (child.tagName != "tag") {
  352.             continue;
  353.           }
  354.           result.push({
  355.             tag: child.getAttribute("tag"),
  356.             count: child.getAttribute("count")
  357.           });
  358.         }
  359.         aFlockListener.onSuccess(result, "success");
  360.       }
  361.     }
  362.   };
  363.  
  364.   this.call("tags/get", {}, deliciousAPIListener, aAuth);
  365. };
  366.  
  367. /**
  368.  * Helper function to test the API call response.
  369.  * @param nsIDOMDocument aXML (in)
  370.  * @param AString aCompareValue (in)
  371.  * @return boolean
  372.  *    True  - Response indicates successful API call
  373.  *    False - Response is missing, wrong, or reports an error
  374.  */
  375. DeliciousAPI.prototype.isExpectedResponse =
  376. function deliciousAPI_isExpectedResponse(aXML, aCompareValue) {
  377.   // Validate the nsIDOMDocument response.
  378.   if (!aXML ||
  379.       !aXML.documentElement ||
  380.        aXML.documentElement.tagName != aCompareValue)
  381.   {
  382.     return false;
  383.   }
  384.   return true;
  385. };
  386.